字节级别的操作

    ByteBuf 使用zero-based 的 indexing(从0开始的索引),第一个字节的索引是 0,最后一个字节的索引是 ByteBuf 的 capacity - 1,下面代码是遍历 ByteBuf 的所有字节:

    Listing 5.6 Access data

    注意通过索引访问时不会推进 readerIndex (读索引)和 writerIndex(写索引),我们可以通过 ByteBuf 的 readerIndex(index) 或 writerIndex(index) 来分别推进读索引或写索引

    顺序访问索引

    ByteBuf 提供两个指针变量支付读和写操作,读操作是使用 readerIndex(),写操作时使用 writerIndex()。这和JDK的ByteBuffer不同,ByteBuffer只有一个方法来设置索引,所以需要使用 flip() 方法来切换读和写模式。

    ByteBuf 一定符合:0 <= readerIndex <= writerIndex <= capacity。

    Figure 5.3 ByteBuf internal segmentation

    1.字节,可以被丢弃,因为它们已经被读

    2.还没有被读的字节是:“readable bytes(可读字节)”

    3.空间可加入多个字节的是:“writeable bytes(写字节)”

    可丢弃字节的字节

    标有“可丢弃字节”的段包含已经被读取的字节。他们可以被丢弃,通过调用discardReadBytes() 来回收空间。这个段的初始大小存储在readerIndex,为 0,当“read”操作被执行时递增(“get”操作不会移动 readerIndex)。

    图5.4示出了在 图5.3 中的缓冲区中调用 discardReadBytes() 所示的结果。你可以看到,在丢弃字节段的空间已变得可用写。需要注意的是不能保证对可写的段之后的内容在 discardReadBytes() 方法之后已经被调用。

    Figure 5.4 ByteBuf after discarding read bytes.

    字节级别的操作 - 图2

    1.字节尚未被读出(readerIndex 现在 0)。
    2.可用的空间,由于空间被回收而增大。

    ByteBuf.discardReadBytes() 可以用来清空 ByteBuf 中已读取的数据,从而使 ByteBuf 有多余的空间容纳新的数据,但是discardReadBytes() 可能会涉及内存复制,因为它需要移动 ByteBuf 中可读的字节到开始位置,这样的操作会影响性能,一般在需要马上释放内存的时候使用收益会比较大。

    ByteBuf 的“可读字节”分段存储的是实际数据。新分配,包装,或复制的缓冲区的 readerIndex 的默认值为 0 。任何操作,其名称以 “read” 或 “skip” 开头的都将检索或跳过该数据在当前 readerIndex ,并且通过读取的字节数来递增。

    如果所谓的读操作是一个指定 ByteBuf 参数作为写入的对象,并且没有一个目标索引参数,目标缓冲区的 writerIndex 也会增加了。例如:

    如果试图从缓冲器读取已经用尽的可读的字节,则抛出IndexOutOfBoundsException。清单5.8显示了如何读取所有可读字节。

    Listing 5.7 Read all data

    1. //遍历缓冲区的可读字节
    2. ByteBuf buffer= ...;
    3. while (buffer.isReadable()) {
    4. System.out.println(buffer.readByte());
    5. }

    这段是未定义内容的地方,准备好写。一个新分配的缓冲区的 writerIndex 的默认值是 0 。任何操作,其名称 “write”开头的操作在当前的 writerIndex 写入数据时,递增字节写入的数量。如果写操作的目标也是 ByteBuf ,且未指定源索引,则源缓冲区的 readerIndex 将增加相同的量。例如:

    如果试图写入超出目标的容量,则抛出 IndexOutOfBoundException。

    下面的例子展示了填充随机整数到缓冲区中,直到耗尽空间。该方法writableBytes() 被用在这里确定是否存在足够的缓冲空间。

    Listing 5.8 Write data

    1. //填充随机整数到缓冲区中
    2. while (buffer.writableBytes() >= 4) {
    3. buffer.writeInt(random.nextInt());
    4. }

    索引管理

    在 JDK 的 InputStream 定义了 mark(int readlimit) 和 reset()方法。这些是分别用来标记流中的当前位置和复位流到该位置。

    同样,您可以设置和重新定位ByteBuf readerIndex 和 writerIndex 通过调用 markReaderIndex(), markWriterIndex(), resetReaderIndex() 和 resetWriterIndex()。这些类似于InputStream 的调用,所不同的是,没有 readlimit 参数来指定当标志变为无效。

    调用 clear() 可以同时设置 readerIndex 和 writerIndex 为 0。注意,这不会清除内存中的内容。让我们看看它是如何工作的。 (图5.5图重复5.3 )

    Figure 5.5 Before clear() is called

    调用之前,包含3个段,下面显示了调用之后

    Figure 5.6 After clear() is called

    字节级别的操作 - 图4

    现在 整个 ByteBuf 空间都是可写的了。

    clear() 比 discardReadBytes() 更低成本,因为他只是重置了索引,而没有内存拷贝。

    有几种方法,以确定在所述缓冲器中的指定值的索引。最简单的是使用 indexOf() 方法。更复杂的搜索执行以 ByteBufProcessor 为参数的方法。这个接口定义了一个方法,boolean process(byte value),它用来报告输入值是否是一个正在寻求的值。

    ByteBufProcessor 定义了很多方便实现共同目标值。例如,假设您的应用程序需要集成所谓的“Flash sockets”,将使用 NULL 结尾的内容。调用

    1. forEachByteByteBufProcessor.FIND_NUL

    通过减少的,因为少量的
    “边界检查”的处理过程中执行了,从而使 消耗 Flash 数据变得 编码工作量更少、效率更高。

    下面例子展示了寻找一个回车符,\ r的一个例子。

    Listing 5.9 Using ByteBufProcessor to find \r

    衍生的缓冲区

    “衍生的缓冲区”是代表一个专门的展示 ByteBuf 内容的“视图”。这种视图是由 duplicate(), slice(), slice(int, int),readOnly(), 和 order(ByteOrder) 方法创建的。所有这些都返回一个新的 ByteBuf 实例包括它自己的 reader, writer 和标记索引。然而,内部数据存储共享就像在一个 NIO 的 ByteBuffer。这使得衍生的缓冲区创建、修改其
    内容,以及修改其“源”实例更廉价。

    ByteBuf 拷贝

    如果需要已有的缓冲区的全新副本,使用 copy() 或者 copy(int, int)。不同于派生缓冲区,这个调用返回的 ByteBuf 有数据的独立副本。

    若需要操作某段数据,使用 slice(int, int),下面展示了用法:

    Listing 5.10 Slice a ByteBuf

    1. Charset utf8 = Charset.forName("UTF-8");
    2. ByteBuf sliced = buf.slice(0, 14); //2
    3. System.out.println(sliced.toString(utf8)); //3
    4. buf.setByte(0, (byte) 'J'); //4
    5. assert buf.getByte(0) == sliced.getByte(0);

    1.创建一个 ByteBuf 保存特定字节串。

    2.创建从索引 0 开始,并在 14 结束的 ByteBuf 的新 slice。

    3.打印 Netty in Action

    4.更新索引 0 的字节。

    5.断言成功,因为数据是共享的,并以一个地方所做的修改将在其他地方可见。

    下面看下如何将一个 ByteBuf 段的副本不同于 slice。

    Listing 5.11 Copying a ByteBuf

    1. Charset utf8 = Charset.forName("UTF-8");
    2. ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); //1
    3. System.out.println(copy.toString(utf8)); //3
    4. buf.setByte(0, (byte) 'J'); //4
    5. assert buf.getByte(0) != copy.getByte(0);

    1.创建一个 ByteBuf 保存特定字节串。

    2.创建从索引0开始和 14 结束 的 ByteBuf 的段的拷贝。

    4.更新索引 0 的字节。

    5.断言成功,因为数据不是共享的,并以一个地方所做的修改将不影响其他。

    代码几乎是相同的,但所 衍生的 ByteBuf 效果是不同的。因此,使用一个 slice 可以尽可能避免复制内存。

    读/写操作主要由2类:

    • gget()/set() 操作从给定的索引开始,保持不变
    • read()/write() 操作从给定的索引开始,与字节访问的数量来适用,递增当前的写索引或读索引

    ByteBuf 的各种读写方法或其他一些检查方法可以看 ByteBuf 的 API,下面是常见的 get() 操作:

    Table 5.1 get() operations

    常见 set() 操作如下

    Table 5.2 set() operations

    下面是用法:

    Listing 5.12 get() and set() usage

    1.创建一个新的 ByteBuf 给指定 String 保存字节

    2.打印的第一个字符,

    3.存储当前 readerIndex 和 writerIndex

    4.更新索引 0 的字符B

    5.打印出的第一个字符,现在B

    6.这些断言成功,因为这些操作永远不会改变索引

    现在,让我们来看看 read() 操作,对当前 readerIndex 或
    writerIndex 进行操作。这些用于从 ByteBuf 读取就好像它是一个流。 (对应的 write() 操作用于“追加”到 ByteBuf )。下面展示了常见的  read() 方法。

    Table 5.3 read() operations

    每个 read() 方法都对应一个 write()。

    Table 5.4 Write operations

    Listing 5.13 read()/write() operations on the ByteBuf

    1. Charset utf8 = Charset.forName("UTF-8");
    2. ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); //1
    3. System.out.println((char)buf.readByte()); //2
    4. int readerIndex = buf.readerIndex(); //3
    5. int writerIndex = buf.writerIndex(); //4
    6. buf.writeByte((byte)'?'); //5
    7. assert writerIndex != buf.writerIndex();

    1.创建一个新的 ByteBuf 保存给定 String 的字节。

    2.打印的第一个字符,N

    3.存储当前的 readerIndex

    4.保存当前的 writerIndex

    5.更新索引0的字符

    6.此断言成功,因为 writeByte() 在 5 移动了 writerIndex

    更多操作

    UnsupportedOperationException.